#include "FileUtil.h"
#include "StrUtil.h"
#include "xUtils.h"

using namespace x2lib;

bool FileUtil::MakeDirs(const char* pszDirs)
{
	if (NULL == pszDirs)
		return false;

	char szDirs[1024];
	strcpy(szDirs, pszDirs);
	int iLen = strlen(szDirs) - 1;
	if (szDirs[iLen] != '/' && szDirs[iLen] != '\\')
	{
		szDirs[iLen+1] = '/';
	}
#ifdef __X2LIB_LINUX__
	if (opendir(szDirs) != NULL)
		return true;
#endif

	iLen = strlen(szDirs) - 1;
	for (uint32_t i = 0; i < iLen; i++)
	{
		if ((szDirs[i] == '\\' || szDirs[i] == '/') && i != 0) // etc /logout/writ/
		{
			szDirs[i] = '\0';
			if (0 != access(szDirs, 0))
			{
#ifdef __X2LIB_WIN32__
				if (0 != mkdir(szDirs))
#elif defined __X2LIB_LINUX__
				if (-1 == mkdir(szDirs, 0755))
#endif
				{
					return false;
				}
			}
			szDirs[i] = '/';
		}
	}

	return true;
}

void FileUtil::EnumFiles(const char *pszDir, int depth, ENUMFILESPROC enumProc, void* userData)
{
    if(depth == 0) { return; }
	else if(depth > 0) { depth -= 1; }
    
#ifdef __X2LIB_WIN32__
    WIN32_FIND_DATAA findData;
    char fullPath[MAX_PATH];

    snprintf(fullPath, MAX_PATH, "%s\\*", pszDir);
    HANDLE hFind = FindFirstFileA(fullPath, &findData);

    if (hFind == INVALID_HANDLE_VALUE)
    {
        return;
    }

    do
    {
        if (strcmp(findData.cFileName, ".") == 0 || strcmp(findData.cFileName, "..") == 0)
        {
            continue;
        }

        snprintf(fullPath, MAX_PATH, "%s\\%s", pszDir, findData.cFileName);

        if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        {
            if(true == enumProc(false, fullPath, 0, userData)) { break; }
            EnumFiles(fullPath, depth, enumProc, userData);
        }
        else
        {
            if(true == enumProc(true, fullPath, findData.nFileSizeLow, userData)) { break; }
        }
    } while (FindNextFileA(hFind, &findData) != 0);

    FindClose(hFind);
#else
    DIR* dir = opendir(pszDir);
    if (dir == NULL)
    {
        return;
    }
    struct dirent *entry;
    char fullPath[PATH_MAX];
    while ((entry = readdir(dir)) != NULL)
    {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
        {
            continue;
        }

        snprintf(fullPath, PATH_MAX, "%s/%s", pszDir, entry->d_name);

        if (entry->d_type == DT_DIR)
        {
            if(true == enumProc(false, fullPath, 0, userData)) { break; }
            EnumFiles(fullPath, depth, enumProc, userData);
        }
        else
        {
            if(true == enumProc(true, fullPath, entry->d_reclen, userData)) { break; }
        }
    }
    closedir(dir);
#endif
}

uint32_t FileUtil::GetFileSize(const char *szFilePath)
{
	if (FILE *fp = fopen(szFilePath, "rb"))
	{
		fseek(fp, 0, SEEK_END);
		uint32_t size = ftell(fp);
		fclose(fp);
		return size;
	}
	return 0;
}

bool FileUtil::CopyDirFiles(const char* src, const char* dst, bool ignoreError)
{
	auto copyFile = [](const char* pszFile1, const char* pszFile2)->bool {
		int index2 = FindLastSlash(pszFile2);
		((char*)pszFile1)[index2] = 0;
		if (!MakeDirs(pszFile2)) 
		{
			((char*)pszFile1)[index2] = '/';
			return false; 
		}
		((char*)pszFile1)[index2] = '/';

		FILE* fp1 = fopen(pszFile1, "rb");
		FILE* fp2 = fopen(pszFile2, "wb+");
		bool isSucc = false;
		if (fp1 && fp2)
		{
			char pBytes[1024];
			uint32_t uBytes = 0;
			while ((uBytes = fread(pBytes, 1, 1024, fp1)) > 0)
			{
				fwrite(pBytes, uBytes, 1, fp2);
			}
			isSucc = true;
		}
		if (fp1) { fclose(fp1); }
		if (fp2) { fclose(fp2); }

		return isSucc;
	};

	int type = JudgeDirOrFile(src);
	if (1 == type)
	{
		return copyFile(src, dst);
	}
	else if (-1 == type)
	{
		const char* pszDir1 = src;
		const char* pszDir2 = dst;
		auto enumProc = [&](bool isFile, const char* pszItem, uint64_t length, void* p)->bool {
			bool* pisSucc = (bool*)p;
			if (isFile)
			{
				char szItem[1024];
				StrUtil::Replace(pszItem, szItem, sizeof(szItem), pszDir1, pszDir2);
				int index = FileUtil::FindLastSlash(pszDir2);

				(*pisSucc) &= copyFile(pszItem, szItem);
			}
			return true;
		};

		bool isSucc = true;
		EnumFiles(src, -1, xUtils::lambda2func<bool(*)(bool, const char*, uint64_t, void*)>(enumProc), &isSucc);
		return isSucc;
	}

	return false;
}

bool FileUtil::ReadWrite(const char *szFilePath, void *pData, int nData,bool bRead)
{
	if (FILE *fp = fopen(szFilePath, bRead?"rb":"wb"))
	{
		uint32_t dwReal = 0;
		if (bRead) { dwReal = fread(pData, 1, nData, fp); }
		else { dwReal = fwrite(pData, 1, nData, fp);}
		bool isEnd = feof(fp);
		fclose(fp);
		return dwReal >= (uint32_t)nData || isEnd;
	}
	return false;
}
#if 0
// 此函数不应该存在，需修改 xktesla@2017-5-7 15:37:18
bool FileUtil::ChangeDirectory(char *szOld, char *szRel, char *szFull, int nFull)
{
	char szCurPath[512];
	char szOldCurPath[512];

	// 检测oldPath是否合法，并获取其绝对路径
#ifdef __X2LIB_WIN32__
	GetCurrentDirectory(sizeof(szOldCurPath), szOldCurPath);
	if (SetCurrentDirectory(szOld))
		GetCurrentDirectory(sizeof(szCurPath), szCurPath);
	SetCurrentDirectory(szOldCurPath);
#elif defined __X2LIB_LINUX__
	{
		FILE *pp = popen("pwd", "r");
		fgets(szOldCurPath, sizeof(szOldCurPath), pp);
		chdir(szOld);
		getcwd(szCurPath, sizeof(szCurPath));
		chdir(szOldCurPath);
		pclose(pp);
	}
#endif
	// 修正szCurPath，并附加newPath
	int nCur = strlen(szCurPath);
	if (szCurPath[nCur - 1] != '/' && szCurPath[nCur - 1] != '\\')
		strcat(szCurPath, "/");
	if (strrchr(szRel, ':') != NULL)
	{
		strcpy(szCurPath, szRel);
	}
	else
	{
		if (szRel[0] == '/' || szRel[0] == '\\')
			strcat(szCurPath, &szRel[1]);
		else
			strcat(szCurPath, szRel);
	}
	// 二次修正szCurPath
	nCur = strlen(szCurPath);
	if (szCurPath[nCur - 1] != '/' && szCurPath[nCur - 1] != '\\')
		strcat(szCurPath, "/");

	// 检测szCurPath是否合法，并获取其绝对路径
#ifdef __X2LIB_WIN32__
	GetCurrentDirectory(sizeof(szOldCurPath), szOldCurPath);
	if (SetCurrentDirectory(szCurPath))
		GetCurrentDirectory(sizeof(szCurPath), szCurPath);
	SetCurrentDirectory(szOldCurPath);
#elif defined __X2LIB_LINUX__
	{
		FILE *pp = popen("pwd", "r");
		fgets(szOldCurPath, sizeof(szOldCurPath), pp);
		chdir(szCurPath);
		getcwd(szCurPath, sizeof(szCurPath));
		chdir(szOldCurPath);
		pclose(pp);
	}
#endif
	nCur = strlen(szCurPath);
	if (szCurPath[nCur - 1] != '/' && szCurPath[nCur - 1] != '\\')
		strcat(szCurPath, "/");

	strcpy(szFull, szCurPath);
#if 0
#ifdef __X2LIB_WIN32__
	if (SetCurrentDirectory(szCurPath))
		return GetCurrentDirectory(curPathLen, curPath)>0;
#elif defined __X2LIB_LINUX__
	if (chdir(szCurPath) == 0)
		return 0 != getcwd(curPath, curPathLen);
#endif
#endif
	return true;
}

bool FileUtil::TestDirectory(char *szDir, int mode)
{
	return 0 == access(szDir, mode);
}
#endif

int FileUtil::FindLastSlash(const char* pszPath, char* slash)
{
	const char* pFile = strrchr(pszPath, '\\');
	if (pFile == nullptr)
		pFile = strrchr(pszPath, '/');
	
	if (pFile)
	{
		if (slash) { *slash = *pFile; }
		return (pFile - pszPath);
	}
	return -1;
}

int FileUtil::JudgeDirOrFile(const char *szFile)
{
#ifdef __X2LIB_WIN32__ 
	uint32_t dwAttr = ::GetFileAttributes(szFile);  //得到文件属性
	if (dwAttr == 0xFFFFFFFF)    // 文件或目录不存在
		return 0;
	else if (dwAttr&FILE_ATTRIBUTE_DIRECTORY)  // 如果是目录
		return -1;
	else
		return 1;
#elif defined __X2LIB_LINUX__
	struct stat info;
	if (0 != lstat(szFile, &info))
		return 0;
	if (S_ISDIR(info.st_mode))
		return -1;
	else// if (S_ISREG(info.st_mode))
		return 1;
	//else
	//return 0;
#endif
	return 0;
}

const char* FileUtil::CalculatePath(const char* pszPath1, const char* pszPath2, char* pszPath3)
{
	static char szPath3[1024];
	char szPath1[1024], szPath2[1024];

	if (!pszPath3) { pszPath3 = szPath3; }

	StrUtil::Replace(pszPath1, szPath1, sizeof(szPath1), "\\", "/");
	StrUtil::Replace(pszPath2, szPath2, sizeof(szPath2), "\\", "/");
	int lenPath1 = strlen(szPath1);
	int lenPath2 = strlen(szPath2);
	if (szPath1[lenPath1 - 1] == '/') { szPath1[--lenPath1] = 0; }
	if (szPath2[lenPath2 - 1] == '/') { szPath2[--lenPath2] = 0; }

#ifdef __X2LIB_WIN32__
	if (strchr(szPath2, ':'))
	{
		strcpy(szPath1, szPath2);
	}
#elif defined __X2LIB_LINUX__
	if (szPath2[0] == '/')
	{
		strcpy(szPath1, szPath2);
	}
#endif
	else
	{
		// 此时strOld=X: or X:/111/222 【结尾不带'/'】
		for (int i = 0; i < lenPath2 && !strchr(szPath2, ':'); i++)
		{
			if (&szPath2[i] == strstr(&szPath2[i], ".."))
			{ // 上一级
				char *p = strrchr(szPath1, '/');
				if (p) *p = 0;
				i += 1;
			}
			else if (szPath2[i] != '.' && szPath2[i] != '/')
			{ // 下一级
				char* p = strchr(&szPath2[i], '/');
				if (p != NULL) *p = 0;
				sprintf(szPath1, "%s/%s", szPath1, &szPath2[i]);
				i += strlen(&szPath2[i]);
			}
		}
	}

	if (0 == JudgeDirOrFile(szPath1))
	{
		return nullptr;
	}

	if (szPath2[lenPath2 - 1] == '/')
	{
		strcat(szPath1, "/");
	}
#ifdef __X2LIB_WIN32__
	StrUtil::Replace(szPath1, szPath1, sizeof(szPath1), "/", "\\");
#endif

	strncpy(pszPath3, szPath1, 1024);

	return pszPath3;
}

bool deleteFileProc(bool isFile, const char* pszItem, uint64_t length, void* p)
{
	bool *pRet = (bool*)p;
	if (isFile)
	{
#ifdef __X2LIB_WIN32__
		DeleteFileA(pszItem);
#elif defined __X2LIB_LINUX__
		remove(pszItem);
#endif
	}
	else
	{
		FileUtil::EnumFiles(pszItem, -1, deleteFileProc, p);
#ifdef __X2LIB_WIN32__
		RemoveDirectoryA(pszItem);
#elif defined __X2LIB_LINUX__
		remove(pszItem);
#endif
	}

	return true;
}

/*
EROFS  欲写入的文件为只读文件。
EFAULT  参数filename 指针超出可存取内存空间。
ENAMETOOLONG  参数filename 太长。
ENOMEM  核心内存不足。
ELOOP  参数filename 有过多符号连接问题。
EIO I/O  存取错误。
*/
bool FileUtil::DeleteDirFiles(const char* pszPath, bool isDelCurDir)
{
	bool bRet = false;
	//return 0==remove(lpPath);
	EnumFiles(pszPath, -1, deleteFileProc, &bRet);

	if (isDelCurDir)
	{
#ifdef __X2LIB_WIN32__
		bRet &= (bool)RemoveDirectoryA(pszPath);
#elif defined __X2LIB_LINUX__
		bRet &= remove(pszPath);
#endif
	}
	return bRet;
}

